/*
 * main.c
 */

/***********************************************************************************
 * 	Reverse Geocache
 * 	Connections:
 *
 * 		PC4 <-- GPS-Rx
 * 		PC5 --> GPS-Tx
 *
 * 		PD0 <-- PushButton
 * 		PD1 --> GPS Power
 * 		PD2 --> LCD Backlight power
 * 		PD3 --> Lock	??
 * 		PD6 --> Unlock	??
 *
 * 		PE4 <-> I2C SCL
 * 		PE5 <-> I2C SDA
 *
 *
 *********************************************************************************/

#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_types.h"
#include "inc/tm4c123gh6pge.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "inc/hw_memmap.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>


#include "I2C_LCD.h"
#include "UART.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define MESSAGE_TYPE_LENGTH	 5
#define MESSAGE_LENGTH 		200
#define DEG_TO_RAD 			M_PI/180
#define LOCKED				1
#define UNLOCKED			0
#define TRUE				1
#define FALSE				0


/**************************************************************************************/
/**************************************************************************************/
																					/**/
// Wouldn't recommend making this less than say 10.0								/**/
#define	UNLOCK_RANGE		20.0	//radius in metres								/**/
																					/**/
//Lat/Long to be entered in decimal degrees											/**/
#define	LATITUDE			-34.862055												/**/
#define	LONGITUDE			138.645750												/**/
																					/**/
																					/**/
/*************************************************************************************/
/*************************************************************************************/


#define	BUTTON_LIMIT_TIME	5000000

char LockStatus = UNLOCKED;
char GPSLockNew = TRUE;
char EnableDisplay = FALSE;
char DisplayOn = FALSE;

volatile unsigned int RxBufferIndex, len=8;
char RxBuffer[200];

uint8_t stringAvailable = 0, GPSValid, NewDataAvailable;
volatile unsigned long ButtonPressTime;

struct GPS_GPGGA{
	float UTCTime;
	float Latitude;
	char NSIndicator;
	float Longitude;
	char EWIndicator;
	unsigned int Quality;
	unsigned int Satellites;
	float HDOP;
	float Altitude;
	char AltUnits;	//will be m for metres
	float GeoidHeight;
	char GUnits;	//will be m for metres
	float DGPSTime;	//will be blank
	float DGPSID;	//will be blank
};

struct GeoData{
	float Latitude;
	float Longitude;
	float TargetLatitude;
	float TargetLongitude;
	float Range;
};


struct GPS_GPGGA GPGGA;
struct GeoData Geo;


void PORTD_INT(void){
	unsigned long PortD_Status;

	//Get the interrupt status
	PortD_Status = GPIO_PORTD_MIS_R;

	//debounce doesnt actually matter for this application

	//Clear the asserted interrupts
	GPIO_PORTD_ICR_R = PortD_Status;


	//Turn on GPS & LCD backlight
	GPIO_PORTD_DATA_R &= ~(GPIO_PIN_1);
	GPIO_PORTD_DATA_R &= ~(GPIO_PIN_2);

	EnableDisplay = TRUE;

	/* Enable Timer 1A */
	GPSLockNew = TRUE;
	WTIMER5_TAILR_R = 300000;
	WTIMER5_CTL_R |= 0x00000001;

}


void TIMER5_A_INT(void){
	unsigned long Timer5_Status;

	//Get the interrupt status
	Timer5_Status = WTIMER5_MIS_R;

	//Clear the asserted interrupts
	WTIMER5_ICR_R = Timer5_Status;

	//turn off GPS,LCD Backlight
	GPIO_PORTD_DATA_R |= GPIO_PIN_1 | GPIO_PIN_2;

	EnableDisplay = FALSE;

}

/* UART4 Interrupt Handler */
void UART4_INT(void){
	unsigned long UART4_Status;
	unsigned int j = 0;
	unsigned char character = ' ';

	//Disable UART4 interrupts while in the interrupt handler
	NVIC_EN1_R &= ~NVIC_EN0_INT28;

	//Get the interrupt status
	UART4_Status = UART4_MIS_R;

	//Clear the asserted interrupts
	UART4_ICR_R = UART4_Status;

	while(j <= 200){
		while(UART4_FR_R & UART_FR_RXFE);
		character = UART4_DR_R;
		if(character == 0x0D || character == 0x0A){
			RxBuffer[j] = character;
			stringAvailable = 1;
			NVIC_EN1_R |= NVIC_EN0_INT28;
			break;
		} else if(character == '$'){
			j=0;
		}
		RxBuffer[j++] = character;
		UART4_DR_R = character;
	}
	NVIC_EN1_R |= NVIC_EN0_INT28;
	stringAvailable = 1;
}//UART4_INT


int parseString(char * inputString){


	uint8_t stringStart, i,j=0, completeSentence=0;

	char messageType[MESSAGE_TYPE_LENGTH], message[MESSAGE_LENGTH];

	/* Initialise message and message type buffers */
	for(i=0;i<=MESSAGE_TYPE_LENGTH;i++){
		messageType[i]='\0';
	}

	for(i=0;i<=MESSAGE_LENGTH;i++){
		message[i]='\0';
	}


	/* Load read input string into buffers */
	for(stringStart=0;stringStart<strlen(inputString);stringStart++){
		if(inputString[stringStart] == '$'){
			for(i=1;i<=5;i++){
				messageType[i-1] = inputString[stringStart + i];
			}

			i = stringStart + 7;
			while(!(inputString[i] == '*') && !(inputString[i] == 10) && j<MESSAGE_LENGTH){
				message[j++]=inputString[i++];
			}

			i++;

			completeSentence = 1;
			break;
		}
	}



	if(!strcmp(messageType,"GPGGA")){
		sscanf(message,"%f,%f,%c,%f,%c,%d,%d,%f,%f,%c,%f,%c,%f,%f",
							&(GPGGA.UTCTime),
							&(GPGGA.Latitude),
							&(GPGGA.NSIndicator),
							&(GPGGA.Longitude),
							&(GPGGA.EWIndicator),
							&(GPGGA.Quality),
							&(GPGGA.Satellites),
							&(GPGGA.HDOP),
							&(GPGGA.Altitude),
							&(GPGGA.AltUnits),
							&(GPGGA.GeoidHeight),
							&(GPGGA.GUnits),
							&(GPGGA.DGPSTime),
							&(GPGGA.DGPSID));
	}

	GPSValid = (GPGGA.Latitude > 0 && GPGGA.Longitude > 0);

	NewDataAvailable = 1;
	return completeSentence;
}

void I2CTransmitSingle(unsigned int Address, char *Data){

	I2C2_MSA_R = Address << 1;

	/* Read/write register */
	I2C2_MDR_R = *Data;		//0b10100101

	I2C2_MCS_R = 0x07;		//transmit a single byte

	//Wait until transmission complete
	while(I2C2_MCS_R & I2C_MCS_BUSY){	}

}

void I2CTransmitMultiple(unsigned int Address, char *TransmitData, unsigned int NumBytes){

	unsigned index = 1;
	I2C2_MSA_R = Address << 1;
	I2C2_MDR_R = *TransmitData++;
	I2C2_MCS_R = 0x03;

	for(;index<NumBytes;index++){

		//Wait until current transmission complete
		while(I2C2_MCS_R & I2C_MCS_BUSY){	}



		/* Check for an error */
		if(I2C2_MCS_R & I2C_MCS_ERROR){
			if(I2C2_MCS_R & I2C_MCS_ARBLST){
				I2C2_MCS_R = 0x04;		//Stop Byte
			}
			//ErrorService();
			return;


		}

		//Send next data byte
		I2C2_MDR_R = *TransmitData++;
		I2C2_MCS_R = 0x01;
	}

	//End transmission
	I2C2_MCS_R = 0x05;

	//Wait until transmission complete
	while(I2C2_MCS_R & I2C_MCS_BUSY){	}

}

void I2C2Init(void){

	// Enable PORTE peripheral
	SYSCTL_RCGCGPIO_R |= SYSCTL_RCGCGPIO_R4;							//Add PORTE to enabled peripherals

	// Enable I2C1 Peripheral
	SYSCTL_RCGCI2C_R |= SYSCTL_RCGCI2C_R2;


	// Configure PORTE
	GPIO_PORTE_AFSEL_R |= (GPIO_PIN_4|GPIO_PIN_5);						//Alternate function on pins 4,5
	GPIO_PORTE_ODR_R |= (GPIO_PORTE_ODR_R & ~GPIO_PIN_4)|GPIO_PIN_5;	//Pin 5 open drain, Pin 4 not open drain
	GPIO_PORTE_PCTL_R |= GPIO_PCTL_PE4_I2C2SCL|GPIO_PCTL_PE5_I2C2SDA;	//Alternate function is I2C2
	GPIO_PORTE_DEN_R |= GPIO_PIN_4|GPIO_PIN_5;							//Digital Enable pins 6, 7
	GPIO_PORTE_PUR_R |= GPIO_PIN_4|GPIO_PIN_5;							//Pins 4, 5 pull up resistor



	// Configure I2C1
	I2C2_MCR_R = 0x0010;

	/******************************************************************************
	 * TPR = (System Clock/(2*(SCL_LP + SCL_HP)*SCL_CLK))-1;
	 * 	SCL_LP fixed at 6
	 * 	SCL_HP fixed at 4
	 *
	 * TPR = (40MHz/(2*(6+4)*100kHz))-1;
	 * TPR = 19
	 ******************************************************************************/
	I2C2_MTPR_R = 19;

}


void Timer5_Init(void){

	/**********************/
	/*  Configure TIMER5  */
	/**********************/

	/* Enable Wide TIMER5 peripheral */
	SYSCTL_RCGCWTIMER_R |= SYSCTL_RCGCWTIMER_R5;

	// Enable Timer5A interrupts in the NVIC
	NVIC_EN3_R |= NVIC_EN0_INT8;

//	WTIMER5_ICR_R = 0xffffffff;

	/* Disable Timers A and B */
	WTIMER5_CTL_R &= ~(0x00000101);
	/* 32 bit timer */
	WTIMER5_CFG_R = 0x00000004;
	/* Configure as count down, single shot */
	WTIMER5_TAMR_R = 0x00000001;

	/* 16 bit prescaler value */
	/* Given fCPU 40MHz, need a timer tick every 1ms */
	/* 40E6*0.001 = 0d40000 = 0x9C40*/
	WTIMER5_TAPR_R = 0x9C40;

	/* 32 bit match value */
	/* Timer ticks every 1ms, need 300 sec timer */
	/* 300/0.001 = 0d300000 = 0x493E0 */
	WTIMER5_TAILR_R = 300000;

	/* Time Out interrupt mask */
	WTIMER5_IMR_R = 0x00000001;

	WTIMER5_TAR_R = 0;
	WTIMER5_TAV_R = 0;


}

void GPIO_Init(){

	// Enable PORTD peripheral
	SYSCTL_RCGCGPIO_R |= SYSCTL_RCGCGPIO_R3;				//Add PORTD to enabled peripherals

	// Enable PORTD interrupts in the NVIC
	NVIC_EN0_R |= NVIC_EN0_INT3;

//	GPIO_PORTD_ICR_R = 0xffffffff;

	// Configure PORTD
	GPIO_PORTD_DIR_R = (GPIO_PORTD_DIR_R & ~GPIO_PIN_0)|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_6|GPIO_PIN_3;
																				//Pin 0 input, 1,2 output. Maintain other bits
	GPIO_PORTD_DEN_R |= GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_6|GPIO_PIN_3;	//Digital Enable pins 0,1,2
	GPIO_PORTD_PUR_R |= GPIO_PIN_0;												//Pull up on 0
	GPIO_PORTD_PUR_R |= GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_6|GPIO_PIN_3;			//Pull down 1,2,6,7
	GPIO_PORTD_ODR_R |= GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_6|GPIO_PIN_3;			//8mA drive for 1,2,6,7

	GPIO_PORTD_DATA_R = 0xff;

	// Enable interrupts on PORTD.0
	GPIO_PORTD_IEV_R = 0x00;
	GPIO_PORTD_IM_R = 0x01;

}



void DegDecMinToDecDeg(void){

	float tempLat, tempLong;
	double intPartLat, intPartLong;
	double fractPartLat, fractPartLong;

	tempLat = GPGGA.Latitude / 100;
	tempLong = GPGGA.Longitude / 100;

	fractPartLat = modf(tempLat, &intPartLat);
	fractPartLong = modf(tempLong, &intPartLong);

	fractPartLat *= 1.667; // fractPartLat*100/60, convert degressMinutes to decimal degrees
	fractPartLong *= 1.667; // fractPartLong*100/60, convert degressMinutes to decimal degrees

	Geo.Latitude = intPartLat + fractPartLat;
	Geo.Longitude = intPartLong + fractPartLong;

	if(GPGGA.NSIndicator == 'S') Geo.Latitude *= -1;
	if(GPGGA.EWIndicator == 'W') Geo.Longitude *= -1;

}


void Haversine(){
	long r = 6371;

	double dLat = (Geo.Latitude - Geo.TargetLatitude) * DEG_TO_RAD;
	double dLon = (Geo.Longitude - Geo.TargetLongitude) * DEG_TO_RAD;

	double Lat1 = Geo.Latitude * DEG_TO_RAD;
	double Lat2 = Geo.TargetLatitude * DEG_TO_RAD;

	double a, c;

	a = sin(dLat/2) * sin (dLat/2) + sin(dLon/2) * sin(dLon/2) * cos(Lat1) * cos(Lat2);
	c = 2 * atan2( sqrt(a), sqrt(1-a));

	Geo.Range = r * c * 1000;

}

void main(void) {
	
	//Setup the system clock - 40MHz
	SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);

	GPIO_Init();
	UART4_Init();
	I2C2Init();
	Timer5_Init();

	__asm("cpsie");

	unsigned int address=0x3e, i;
	unsigned int LCDResetCount = 0;

	/* Captains log. December 21, 1808
	 *
	 * 7:44pm, the inlet began to snake northward. Everyone was tense, Everyone knew the legend of the great beast that lived in these parts.
	 * The ancient monster, from a time beyond the dawn of time. We followed the inlet as far as we could. We passed many other rusting hulls,
	 * the final reminders of the many souls before us that had tried, and failed, to brave this passage before us.
	 *
	 * The first screams came from the crows nest. They were the first to see angry Crimson Eyes of the Giant Terror squid. Alas, it had already seen us.
	 * We were doomed.
	 *
	 * We began evasive manoeuvres immediately, veering north towards land. If we could get shallow enough the beast may not be able to get its tentacles
	 * around us. Not fast enough. The destruction was immediate. Around about a dozen of us managed to make it to the mangroves. Of this dozen, three
	 * of us made it to solid land. The rest lay in bellies of the crocodiles and other hell beasts in the treacherous mangrove swamp. Davey Jones claimed
	 * 167 good souls that night.
	 *
	 * Captains log. December 28, 1808
	 *
	 * We have been on this God forsaken island for seven nights. Our supplies were running low. A barrel of water had washed ashore. There were many wrecks
	 * on this island. Exploring one, we found a treasure beyond belief. But there could be no way we could get it off the island without a ship. We took it
	 * inland and hid it at the eastern end of the island.
	 * Our only chance of escape would be to attempt a daring swim across the inlet. The land was not far away, but the chances of making it past the soulless
	 * creatures in the river were low.
	 *
	 */

//	Enter target position according to the following notation
//	GPS format should be in DECIMAL DEGREES
//
//	Geo.TargetLatitude = -34.807285;
//	Geo.TargetLongitude = 138.546010;

	Geo.TargetLatitude = LATITUDE;
	Geo.TargetLongitude = LONGITUDE;


	/* This is the roundabout in front of my house, for testing */
//	Geo.TargetLatitude = -34.861675;
//	Geo.TargetLongitude = 138.646013;

	LockStatus = LOCKED;
	GPIO_PORTD_DATA_R &= ~(GPIO_PIN_3);
	for(i=0;i<90000;i++){}
	GPIO_PORTD_DATA_R |= GPIO_PIN_3;

	LCD_Init();
	EnableDisplay = FALSE;
	DisplayOn = FALSE;

	while(1){
//		LCD_Init();

		if(GPIO_PORTD_DATA_R & GPIO_PIN_0){
			if(++ButtonPressTime > BUTTON_LIMIT_TIME){
				ButtonPressTime = BUTTON_LIMIT_TIME;
			}


		}else{
			ButtonPressTime = 0;
		}

		if(stringAvailable){
			parseString(&RxBuffer[0]);
			stringAvailable = 0;
		}

		if(!GPSValid){

			GPSLockNew = TRUE;

			GPGGA.Latitude = 0.0;
			GPGGA.Longitude = 0.0;
			Geo.Range = 10000.0;

			char string1[] = "@Aquiring Lock...";
			char string2[] = "@Please Wait";

			LCD_Row1();
			LCD_ClearLine();
			LCD_Row1();
			I2CTransmitMultiple(address, string1, strlen(string1));

			LCD_Row2();
			LCD_ClearLine();
			LCD_Row2();
			I2CTransmitMultiple(address, string2, strlen(string2));
		}

		if(GPSValid && NewDataAvailable){
			NewDataAvailable = 0;

			if(GPSLockNew == TRUE){
				GPSLockNew = FALSE;
				//time out GPS in 1 min.
				WTIMER5_TAILR_R = 60000;

			}

			DegDecMinToDecDeg();
			Haversine();

			char _latitude[20], _longitude[20];

			sprintf(_latitude,"@%d Satellites",GPGGA.Satellites);
			sprintf(_longitude,"@%dm",(unsigned int)Geo.Range);

			if(LCDResetCount>10){
				LCDResetCount = 0;
				LCD_Init();
			}else{
				LCDResetCount++;
			}

			LCD_Row1();
			LCD_ClearLine();
			LCD_Row1();
			I2CTransmitMultiple(address, _latitude, strlen(_latitude));

			LCD_Row2();
			LCD_ClearLine();
			LCD_Row2();
			I2CTransmitMultiple(address, _longitude, strlen(_longitude));

		}


		if(EnableDisplay == TRUE && DisplayOn == FALSE){
			DisplayOn = TRUE;
			LCD_On();
		}else if (EnableDisplay == FALSE && DisplayOn == TRUE){
			DisplayOn = FALSE;
			LCD_Off();
		}


		if(Geo.Range <= UNLOCK_RANGE && LockStatus == LOCKED){
			LockStatus = UNLOCKED;
			GPIO_PORTD_DATA_R &= ~(GPIO_PIN_6);
			for(i=0;i<90000;i++){}
			GPIO_PORTD_DATA_R |= GPIO_PIN_6;
		} else if(Geo.Range > UNLOCK_RANGE && LockStatus == UNLOCKED){
			LockStatus = LOCKED;
			GPIO_PORTD_DATA_R &= ~(GPIO_PIN_3);
			for(i=0;i<90000;i++){}
			GPIO_PORTD_DATA_R |= GPIO_PIN_3;
		}


	}//End While(1)
}//End Main
